//
// OrientDB API for Sincerity
//
// Copyright 2012-2013 Three Crickets LLC.
//
// The contents of this file are subject to the terms of one of the following
// open source licenses. You can select the license that you prefer but you may
// not use this file except in compliance with one of these licenses.
//
// The LGPL version 3.0:
// http://www.opensource.org/licenses/lgpl-3.0.html
//
// The Apache License version 2.0:
// http://www.opensource.org/licenses/apache2.0.php
//
// Alternatively, you can obtain a royalty free commercial license with less
// limitations, transferable or non-transferable, directly from Three Crickets
// at http://threecrickets.com/
//

/**
 * OrientDB API for Prudence. Uses the OrientDB Java driver.
 * 
 * @namespace
 * @see Visit the <a href="http://www.orientdb.org/">OrientDB site</a>
 * 
 * @author Tal Liron
 */
var OrientDB = OrientDB || function() {
	/** @exports Public as OrientDB */
	var Public = {}

	/**
	 * The logger.
	 * 
	 * @field
	 * @returns {java.util.Logger}
	 */
	Public.logger = application.getSubLogger('orientdb')

	/**
	 * @field
	 * @returns {com.orientechnologies.orient.rhino.OrientTypes}
	 */
	Public.Types = com.orientechnologies.orient.rhino.OrientTypes

	/**
	 * Creates a new OrientDB database.
	 * 
	 * @param {Object|String} params If a string, will count as the connection URI
	 * @param {String} params.uri The connection URI
	 * @param {Boolean|com.orientechnologies.orient.core.db.document.ODatabaseDocumentPool} [params.pool=true]
	 *		 Whether to use a pooled connection; use true to use the global pool, false to use a new connection,
	 *		 or supply a pool instance
	 * @param {String} [params.type='document'] The database type ('document' or 'graph')
	 * @param {String} [params.type='storage'] The storage type ('local')
	 * @param {String} [params.username='root'] The username
	 * @param {String} params.password The password
	 */
	Public.createDatabase = function(params) {
		if (isString(params)) {
			params = {uri: params}
		}
		params = params || {}
		params.type = params.type || 'document'
		params.storage = params.storage || 'local'
		params.username = params.username || 'root'

		var admin = new com.orientechnologies.orient.client.remote.OServerAdmin(params.uri).connect(params.username, params.password)
		try {
			admin.createDatabase(params.type, params.storage)
		}
		finally {
			admin.close()
		}
	}

	/**
	 * An OrientDB database connection.
	 * 
	 * @class
	 * @param {Object|String} config If a string, will count as the connection URI
	 * @param {String} config.uri The connection URI
	 * @param {Boolean|com.orientechnologies.orient.core.db.document.ODatabaseDocumentPool} [config.pool=true]
	 *		 Whether to use a pooled connection; use true to use the global pool, false to use a new connection,
	 *		 or supply a pool instance
	 * @param {String} [config.username='admin'] The username
	 * @param {String} [config.password='admin'] The password
	 */
	Public.Database = function(config) {
		/**
		 * Closes the database connection.
		 */
		this.close = function() {
			this.db.close()
		}

		/**
		 * Creates a new class. Will throw an exception if the class already exists.
		 * 
		 * @param {String} className The class name
		 * @returns {com.orientechnologies.orient.core.metadata.schema.OClass} The class
		 */
		this.newClass = function(className) {
			return this.db.metadata.schema.createClass(className)
		}

		/**
		 * Gets a class if it exists.
		 * 
		 * @param {String} className The class name
		 * @returns {com.orientechnologies.orient.core.metadata.schema.OClass} The class or null if not found
		 */
		this.getClass = function(className) {
			return this.db.metadata.schema.getClass(className)
		}

		this.newDocument = function(className, doc) {
			var instance = this.db.newInstance(className)
			doc = Public.Types.to(doc)
			instance.merge(doc, true, true)
			return instance			
		}

		/**
		 * Begins a transaction.
		 * 
		 * @param {String} [type='optimistic'] Transaction type
		 * @returns {OrientDB.Database} This database
		 */
		this.begin = function(type) {
			if (type) {
				type = com.orientechnologies.orient.core.tx.OTransaction.TXTYPE.valueOf(type)
				this.db.begin(type)
			}
			else {
				this.db.begin()
			}
			return this
		}

		/**
		 * Commits the current transaction.
		 * 
		 * @returns {OrientDB.Database} This database
		 */
		this.commit = function() {
			this.db.commit()
			return this
		}

		/**
		 * Rollbacks the current transaction.
		 * 
		 * @returns {OrientDB.Database} This database
		 */
		this.rollback = function() {
			this.db.rollback()
			return this
		}

		/**
		 * Execute a SQL query.
		 * 
		 * @param {String} sql The SQL query
		 * @param {Object} [params]
		 * @param {String} [params.fetchPlan] The fetch plan
		 * @returns {OrientDB.Results} The query results
		 */
		this.query = function(sql, params) {
			params = params || {}

			var query = new com.orientechnologies.orient.core.sql.query.OSQLSynchQuery(sql)
			if (params.fetchPlan) {
				query.fetchPlan = params.fetchPlan
			}

			return new Public.Results(this.db.query(query))
		}

		/**
		 * Execute a SQL command.
		 * 
		 * @param {String} sql The SQL command
		 * @returns {Number} The number of records updated 
		 */
		this.execute = function(sql) {
			var command = new com.orientechnologies.orient.core.sql.OCommandSQL(sql)
			return this.db.command(command).execute()
		}
		
		//
		// Construction
		//

		if (isString(config)) {
			config = {uri: config}
		}
		config = config || {}
		if ((config.pool === true) || !exists(config.pool)) {
			config.pool = com.orientechnologies.orient.core.db.document.ODatabaseDocumentPool.global()
		}
		else if (config.pool === false) {
			config.pool = null
		}
		config.username = config.username || 'admin'
		config.password = config.password || 'admin'

		if (exists(config.pool)) {
			this.db = config.pool.acquire(config.uri, config.username, config.password)
		}
		else {
			this.db = new com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx(config.uri).open(config.username, config.password)
		}
	}

	/**
	* Query results.
	*
	* @class
	*/
	Public.Results = function(results) {
		/**
		 * @returns {Boolean}
		 */
		this.hasNext = function() {
			return this.iterator.hasNext()
		}

		/**
		 * @returns {Object}
		 */
		this.next = function() {
			var doc = this.iterator.next()
			doc = Public.Types.from(doc)
			return doc
		}

		//
		// Construction
		//

		this.results = results
		this.iterator = results.iterator()
	}

	//
	// Private
	//

	function exists(value) {
		// Note the order: we need the value on the right side for Rhino not to complain about non-JS objects
		return (undefined !== value) && (null !== value)
	}
	
	function isString(value) {
		try {
			return (value instanceof String) || (typeof value == 'string')
		}
		catch (x) {
			return false
		}
	}
	
	//
	// Initialization
	//
	
	// Support for extended JSON
	if (Object.prototype.toString.call(com.threecrickets.rhino.JSON) == '[object JavaClass]') {
		com.threecrickets.rhino.JSON.implementation = new com.orientechnologies.orient.rhino.OrientJsonImplementation()
	}

	return Public
}()